/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   gbx-emu - memory.cpp                                                  *
 *   gbx-emu homepage: http://code.google.com/p/gbx-emu/                   *
 *   Copyright (C) 2009 olejl77@gmail.com                                  *
 *                                                                         *
 *   This program is free software: you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation, either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "memory.h"
#include "gameboy.h"
#include "cpu.h"

/**
 * \class Memory
 * This class handles reading and writing from the Gameboy
 * internal memory.
 *
 * Layout of Gameboy Memory
 * - 0x0000-0x3fff - 16KB ROM Bank 00 (in cartridge, fixed at bank 00)
 * - 0x4000-0x7fff - 16KB ROM Bank 01..NN (in cartridge, switchable bank number)
 * - 0x8000-0x9fff - 8KB Video RAM (VRAM) (switchable bank 0-1 in CGB Mode)
 *                   Only allow writing to VRAM if mode != 3
 * - 0xa000-0xbfff - 8KB External RAM (in cartridge, switchable bank, if any)
 * - 0xc000-0xcfff - 4KB Work RAM Bank 0 (WRAM)
 * - 0xd000-0xdfff - 4KB Work RAM Bank 1 (WRAM) (switchable bank 1-7 in CGB Mode)
 * - 0xe000-0xfdff - Same as C000-DDFF (ECHO) (typically not used)
 *                   The addresses E000-FE00 appear to access the internal RAM the same as C000-DE00. (i.e. If you write a byte to
 *                   address E000 it will appear at C000 and E000. Similarly, writing a byte to C000 will appear at C000 and E000.)
 * - 0xfe00-0xfe9f - Sprite Attribute Table (OAM)
 *                   Only allow writing to OAM if mode == 0 or 1
 * - 0xfea0-0xfeff - Not Usable
 * - 0xff00-0xff7f - I/O Ports
 * - 0xff80-0xfffe - High RAM (HRAM)
 * - 0xffff        - Interrupt Enable Register
**/

/**
 * \fn Memory::Memory(Gameboy *ptr)
 * Contructor.
 * \param *ptr  Pointer to the Gameboy / parent class which created the memory class.
 * required for communicating with other classes.
**/
Memory::Memory(Gameboy *ptr)
{
    _gb = ptr;
    _cpu = NULL;

    // memory
    memory = new u8[0x10000];

    if (memory == NULL) {
        throw "Failed to initialize memory";
    }
}

/**
 * \fn Memory::~Memory()
 * Destructor.
 * Deleting dynamically allocated memory.
 * Sets all pointers to NULL to avoid them being used.
**/
Memory::~Memory()
{
    // free memory
    delete [] memory;

    // Clear to prevent using invalid memory reference.
    memory = NULL;
    _cpu = NULL;
}

/**
 * \fn void Memory::write(u16 addr, u8 value)
 * Write 1 byte to the Gameboy internal memory
 * \param addr  The address to write to. 0x0000 - 0xffff
 * \param value The byte value to write
**/
void Memory::write(u16 addr, u8 value)
{
    if (addr < 0x2000) { // 0000-1FFF - RAM Enable (Write Only)
        _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "RAM Enable. Not implemented! %.2x", value);
    } else if (addr < 0x4000) { // 2000-3FFF - ROM Bank Number (Write Only)
        if (value == 0x00) {
            value = 0x01;
        } else if (value == 0x20) {
            value = 0x21;
        } else if (value == 0x40) {
            value = 0x41;
        } else if (value == 0x60) {
            value = 0x61;
        }
        memcpy(memory + 0x4000, _cart + (value * 0x4000), 0x4000);
    } else if (addr < 0x6000) { // 4000-5FFF - RAM Bank Number - or - Upper Bits of ROM Bank Number (Write Only)
        _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "RAM Bank Number - or - Upper Bits of ROM Bank Number. Not implemented! %.2x", value);
    } else if (addr < 0x8000) { // 6000-7FFF - ROM/RAM Mode Select (Write Only)
        _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "ROM/RAM Mode Select. Not implemented! %.2x", value);
    } else if (addr < 0xa000) {
        if ((memory[STAT] & 0x03) == 0x03) {
            _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "Trying to write to VRAM when mode is: %.1X - LCDC: %.2X", (memory[STAT] & 0x03), memory[LCDC]);
        } else {
            memory[addr] = value;
        }
    } else if (addr < 0xc000) {
        memory[addr] = value;
    } else if (addr < 0xd000) {
        memory[addr] = value;
    } else if (addr < 0xe000) {
        memory[addr] = value;
    } else if (addr < 0xfe00) {
        memory[addr] = value;
    } else if (addr < 0xfea0) {
        if (check_bit(memory[STAT],1)) {
            _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "Trying to write to OAM when mode is: %.1X - LCDC: %.2X - PC: %.4X", (memory[STAT] & 0x03), memory[LCDC], *_cpu->getPC());
        } else {
            memory[addr] = value;
        }
    } else if (addr < 0xff00) {
        memory[addr] = value;
    } else if (addr < 0xff80) {
        if (addr == P1) {
            // bit 0-3 is read only
            // bit 4-5 is the only bits that is writable
            // bit 6-7 is not used. Set high
            memory[P1] = 0xc0  | (value & 0x30) | (memory[P1] & 0x0f);
        } else if(addr == SC) {
            memory[SC] = value | 0x7e;
        } else if(addr == DIV) {
            memory[DIV] = 0;
        } else if(addr == TAC) {
            memory[TAC] = value | 0xf8;
        } else if(addr == IF) {
            memory[IF] = value | 0xe0;
        } else if(addr == NR10) {
            memory[NR10] = value | 0x80;
        } else if(addr == NR24) {
            memory[NR24] = value | 0xb8;
        } else if(addr == NR30) {
            memory[NR30] = value | 0x7f;
        } else if(addr == NR32) {
            memory[NR32] = value | 0x9f;
        // TODO: NR34
        } else if(addr == NR41) {
            memory[NR41] = value | 0xe0;
        } else if(addr == NR44) {
            memory[NR44] = value | 0x3f;
        } else if(addr == NR52) {
            memory[NR52] = value | 0x70;
        } else if(addr == STAT) {
            // bit 0-2 is read only
            memory[STAT] = (memory[STAT] & 0x07) | (value & 0x78) | 0x80;
        } else if(addr == LY) {
            // According to docs, writing will reset it to 0.
            memory[LY] = 0;
        } else if(addr == DMA) {
            memory[DMA] = value;
            if (value <= 0xf1) {
                // TODO: Timing issues?
                // It should take 160 microseconds until the transfer has completed
                // during this time the CPU can access only HRAM (memory at FF80-FFFE)
                memcpy(memory + 0xfe00, memory + (value << 8), OAM_SIZE);
            } else {
                _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "DMA out of range!");
            }
        } else if(addr == TAC) {
            memory[TAC] = value | 0xf8;
        } else if(addr == IF) {
            memory[IF] = value & 0x1f;
        } else {
            memory[addr] = value;
        }
    } else if (addr < 0xffff) {
        memory[addr] = value;
    } else {
        memory[addr] = value | 0xe0;
    }
}

/**
 * \fn void Memory::write_word(u16 addr, u16 value)
 * Write a word (2 byte) to the Gameboy internal memory
 * \param addr  The address to write to. 0x0000 - 0xffff
 * \param value The word value to write
**/
void Memory::write_word(u16 addr, u16 value)
{
    write(addr, (value & 0x00ff));
    write(addr + 1, (value & 0xff00) >> 8);
}

/**
 * \fn u8 Memory::read(u16 addr) const
 * Read byte from the Gameboy internal memory
 * \param addr  The address to read. 0x0000 - 0xffff
**/
u8 Memory::read(u16 addr) const
{
    u8 ret;
    if (addr < 0x04000) { // 0x0000 - 0x3fff - 16KB ROM Bank 00     (in cartridge, fixed at bank 00)
        ret = memory[addr];
    } else if (addr < 0x8000) { // 0x4000 - 0x7fff - 16KB ROM Bank 01..NN (in cartridge, switchable bank number)
        ret = memory[addr];
    } else if (addr < 0xa000) { // 0x8000 - 0x9fff - 8KB Video RAM (VRAM) (switchable bank 0-1 in CGB Mode)
        if ((memory[STAT] & 0x03) != 0x03) {
            // Only allow reading to VRAM if mode != 3
            ret = memory[addr];
        } else {
            _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "Trying to read from VRAM when mode is: %.2X", memory[STAT] & 0x03);
            ret = 0xff;
        }
    } else if (addr < 0xc000) { // 0xa000 - 0xbfff - 8KB External RAM (in cartridge, switchable bank, if any)
        ret = memory[addr];
    } else if (addr < 0xd000) { // 0xc000 - 0xcfff - 4KB Work RAM Bank 0 (WRAM)
        ret = memory[addr];
    } else if (addr < 0xe000) { // 0xd000 - 0xdfff - 4KB Work RAM Bank 1 (WRAM) (switchable bank 1-7 in CGB Mode)
        ret = memory[addr];
    } else if (addr < 0xfe00) { // 0xe000 - 0xfdff - Same as C000-DDFF (ECHO) (typically not used)
        ret = memory[addr];
    } else if (addr < 0xfea0) { // 0xfe00 - 0xfe9f - Sprite Attribute Table (OAM)
        if (!check_bit(memory[STAT],1)) {
            // Only allow reading to OAM if mode == 0 or 1 or LCD disabled
            ret = memory[addr];
        } else {
            _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "Trying to read from OAM when mode is: %.1X - LCDC: %.2X", (memory[STAT] & 0x03), memory[LCDC]);
            ret = 0xff;
        }
    } else if (addr < 0xff00) { // 0xfea0 - 0xfeff - Not Usable
        ret = memory[addr];
    } else if (addr < 0xff80) { // 0xff00 - 0xff7f - I/O Ports
        if (addr == NR23) {
            ret = 0;
            _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "NR23 is write only register.");
        } else if (addr == NR33) {
            ret = 0;
            _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "NR33 is write only register.");
        } else if (addr == DMA) {
            ret = 0;
            _log->Msg(DEBUG_LEVEL, LOG_MEMORY, "DMA is write only register.");
        } else if (addr == P1) {
            u8 tmp = 0xff;
            if (!check_bit(memory[P1], 5)) { // Button Keys
                tmp = _cpu->getButtons();
            } else if (!check_bit(memory[P1], 4)) { // Direction keys
                tmp = _cpu->getDirections();
            }
            memory[P1] = (memory[P1] & 0xf0) | (tmp & 0x0f);
            ret = memory[P1];
        } else {
            ret = memory[addr];
        }
    } else if (addr < 0xffff) { // 0xff80 - 0xfffe - High RAM (HRAM)
        ret = memory[addr];
    } else { // 0xffff - Interrupt Enable Register
        ret = memory[addr];
    }
    return ret;
}

/**
 * \fn u16 Memory::read_word(u16 addr)
 * Read word (2 bytes) from the Gameboy internal memory
 * \param addr  The address to read. 0x0000 - 0xffff
**/
u16 Memory::read_word(u16 addr)
{
    return (read(addr) + ((read(addr + 1)) << 8));
}

/**
 * \fn bool Memory::Load(u8 *cart, ROM_TYPES type)
 * Copy ROM to internal memory
 * \param *cart  Pointer to an unsigned char array containing the complete ROM
 * \param type   The type of ROM. According to ...
**/
bool Memory::Load(u8 *cart, ROM_TYPES type)
{
    _cart = cart;
    rom_type = type;
    bool ret = false;
    switch (rom_type)
    {
    case (ROM_ONLY):
        // read complete rom into memory
        memcpy(memory, _cart, 0x8000);
        ret = true;
        break;
    case (ROM_MBC1):
        // read first 16KBytes of the cartridge ROM.
        memcpy(memory, _cart, 0x4000);
        ret = true;
        break;
    default:
        _log->Msg(WARNING_LEVEL, LOG_MEMORY, "Cart type not supported: %u!", type);
        break;
    }
    return ret;
}

